\subsection{进程和线程}
进程是运行着的程序，是操作系统执行的基本单位。线程则是为节省资源而可以在统一进程中共享资源的一个执行单位。
进程从用户的角度来看是应用程序的一个执行过程。从操作系统的角度看，进程代表的是操作系统分配的内存，CPU时间片等资源的基本单位，是为正在运行的程序提供的运行环境。进程和应用程序的区别在于应用程序作为一个静态文件存储在计算机系统的硬盘等存储空间中，而进程则是处于动态条件下操作系统维护的系统资源管理实体。

进程概念和程序概念最大的不同在于：
\begin{itemize}
\item 进程是动态的程序是静态的
\item 进程有一定的生命期，而程序是指令的集合，本身无运动的含义。没有建立进城的程序不能作为一个独立单位得到操作系统的认可
\item 一个进程只能对应一个程序，一个程序可以对应多个进程。
\end{itemize}
\begin{enumerate}
\item \textbf{进程的产生过程}
\begin{enumerate}
\item 首先复制其父进程的环境配置。
\item 在内核中建立进城结构。
\item 将结构插入到进程列表，便于维护。
\item 分配资源给此进程。
\item 复制父进程的内存映射信息。
\item 管理文件描述符和连接点。
\item 通知父进程
\end{enumerate}
\item \textbf{进程的终止方式}
\begin{itemize}
\item 从main返回。
\item 调用exit
\item 调用\_exit
\item 调用abort
\item 由一个信号终止
\end{itemize}
进程在终止的时候，系统会释放进程所拥有的资源，例如内存，文件符和内核结构。
\item \textbf{进程间的通信}
\begin{itemize}
\item 管道是UNIX族中进程通信的最古老的方式，它利用内核在两个进程间建立通道，他的特点是和文件的操作雷士，仅仅在管道的一端只读，另一端只写。利用读写的方式在进程之间传递数据
\item 共享内存是将内存中的一段地址，在多个进程之间共享。多个进程利用获得的共享内存的地址来直接对内存进行操作。
\item 消息队列则是将在内核中建立一个链表，发送方按一定的标识将数据发送到内核中，内核将其放入链表后，等待接收方的请求。接收方发送请求后，内核按照消息的标识，从内核中将消息从链表中摘下，传递给接收方。消息队列是一种完全的异步操作方式。
\end{itemize}
\item \textbf{进程之间的同步}多个进程之间需要协作完成任务时，经常发生人物之间的依赖现象，从而出现了进程的同步问题。Linux下进程同步的方式主要有消息队列，信号量。信号量是一个共享的表示数据的值。用于多个进程之间操作或者共享资源的保护，是进程之间同步的主要方式。
\item \textbf{进程和线程}
线程和进程的主要区别和联系如下：
\begin{itemize}
\item 进程是操作系统进行资源分配的基本单位，进程拥有完整的虚拟空间，运行系统资源分配任务的时候，除了CPU资源外，不会给线程分配独立的资源，线程所需要的西苑需要共享
\item 线程是进程的一部分，如果没有进行显式的线程分配，可以认为进程市单线程的，如果进程中建立了线程，则可以认为系统是多线程的。
\item 多进程和多线程是两种不同的概念，虽然二者都是并行完成功能。但是多个线程之间想内存，变量等资源可以通过简单的办法共享，多线程则不同，进程间的共享方式有限。
\item 进程有进程控制表PCB，系统通过PCB对进程进行调度；线程有线程控制表TCP，但是，TCB所表示的状态比PCB少的多。
\end{itemize}
\end{enumerate}
\subsection{进程产生的方式}
进程是计算机中云信的基本单位。要产生一个进程，有多种方式，例如使用fork()函数,system()函数,exec()函数等，这些函数的不同在于其运行环境的构造之间存在差异，本质都是对程序运行的各种条件进行设置，在系统之间建立一个可以运行的程序。

\subsection{进程号}
每个进程在初始化的时候，系统都分配了一个ID号，用于标识此进程。在linux中进程号是唯一的，系统可以用这个值来标识一个进程，描述进程的ID好通常叫做PID。
getpid,getppid函数返回当前进程和其父进程的ID。类型pid其实是一个typedef类型的unsigned int。
函数调用的例子如下:
\cppfile{cxxcode/thread/ex1/main.cpp}
\subsubsection{进程的复制fork}
fork()函数以父进程为蓝本复制一个进程，其ID号和父进程不同，在Linux环境下，dork()是以写复制实现的，只有内存等和父进程不同时，其它和父进程共享，只有在父进程或者子进程进行了修改后才重新生成一份。fork()的特点是执行一次，返回两次。在父进程和子进程中返回的是不同的值，父进程中返回的是子进程的ID号，而子进程中则返回0。
\cppfile{cxxcode/thread/ex2/main.cpp}
结果如下（会有一定几率输出一行）:
\begin{bash}
父进程的返回值： 30263 子进程的返回值： 30264 fork的返回值： 0
父进程的返回值： 4420 子进程的返回值： 30263 fork 的返回值 30264
\end{bash}
\subsubsection{system()方式}
执行system()函数时，调用fork(),execve(),waitpid()等函数，其中任意一个调用失败，将导致system()函数调用失败。system()函数的返回值如下:
\begin{itemize}
\item 失败，返回-1
\item sh不能执行的时候返回127
\item 成功的时候返回进程号
\end{itemize}
\cppfile{cxxcode/thread/ex3/main.cpp}
\subsubsection{进程执行exec()函数系列}
使用fork()函数和system()函数的死后，系统中都会建立一个新的进程，执行调用者的操作，而原来的进程还回存在，知道用户显示的退出；而exec()族的函数和之前的fork（）和system()函数不同，exec()族函数会用新进程地带原有的进程，系统会从进的进程运行，新进程的pid和原来进程的pid相同。

exec()族函数共有6个，其原型如下：
\begin{cpp}
#include<unistd.h>
extern char ** environ;
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char *path,const char *arg,...,char * const envp[]);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char * const argv[]);
\end{cpp}
上述6个函数中，只有execve()函数是真正意义上的系统调用，其他的5个函数都是在此基础上包装的库函数。exec()函数族的作用是，在当前系统的可执行路径中根据指定的文件名来找到合适的执行文件名，并用它取代调用进程的内容，即在原来的进程内部运行一个可执行文件。这里的可执行文件既可以是二进制的文件，也可以是可执行的脚本文件。和fork不同exec()函数族的函数执行成功后不会返回，这是应为执行的新程序已经占据了当前进程的空间和资源，这些资源包括：代码段，数据段和堆栈等，他们都已经被新的内容取代，而进程的ID等标志性的信息仍然是原来的东西，即exec()函数族在原来的进程的shell上运行了自己的程序，只有程序调用失败了系统才会返回-1。

使用exec()函数比较普遍的一种方法是先使用fork()函数分叉进程，然后在新的进程中调用exec()函数，这样exec()函数会占用和原来一样的系统资源来运行。

Linux系统针对上述过程专门进行了优化。由于fork()的过程是对原有系统进行复制，然后建立子进程，这些过程都比较耗时间。如果在fork()系统调用之后进行exec()系统调用，系统就不会进行系统复制，而是直接使用exec()指定参数来覆盖原有的进程，上述的方法在Linux系统上叫做“写是复制”，即只有在造成系统的内容发生改变的时候才进行进程的真正更新。

使用execv()函数的例子
\cppfile{cxxcode/thread/ex4/main/main.cpp}
\subsection{进程间的通信和同步}
在Linux下的多个进程间的通信机制叫IPC，它是多个进程间相互沟通的一种方法。在Linux下有多种进程间通信的方法：半双工管道，FIPO(命名管道)，消息队列，信号量，共享内存。
\subsubsection{半双工管道}
使用管道进行进程间的通信
\begin{figure}[!ht]\label{fig:fig_pip1}
\centering
\includegraphics[width=\textwidth]{Pipe_thread.png}
\caption{使用管道进行进程间的通信}
\end{filgure}
\begin{figure}[!ht]\label{fig:fig_pip2}
\centering
\includegraphics[width=\textwidth]{Pipe_thread1.png}
\caption{父子进程间的通信}
\end{filgure}

\cppfile{cxxcode/thread/ex5/pipe/main.cpp}
\subsubsection{管道阻塞和管道操作的原子性}
当管道的写端没有关闭时，如果写请求的字节数目大于阈值PIPE\_BUF,写操作的返回值是管道中目前的数据字节数。如果请求的字节数目不大于PIPE\_BUF，则返回管道中现有数据字节数(此时，管道中数据量小于请求的数据量)；或者返回请求的字节数(此时，管道中的数据量不小于请求的数据量)。

\emph{\textbf{注意：}PIPE\_BUF在include/Linux/limits.h中定义，不同的内核版本会有所不同，Posix.1要求PIPE\_BUF至少512字节}

管道进行写操作的时候，当写入数据的数目小于128K时写入是非原子的，如果把父进程中的两次写入字节数都改成128K，可以发现：写入管道的数据量大于128K字节时，缓冲区的数据将被连续地写入管道，直到数据全部写完为止，如果没有进程读数据则一直阻塞。

可以发现，父进程每次读取10K字节数据读取13次将全部数据读出。最后一次读数据，由于缓冲器中只有8K字节的数据，所以仅读取8K字节。

子进程一次性写入128K字节数据，当父进程全将全部数据读取完毕的时候，子进程的write()函数才返回将写入信息(写入131072个数据，剩余0个数据打印出来)。

上述操作证明管道的操作是阻塞性质。
\subsubsection{命名管道}
命名管道的工作方式和普通管道非常相似，但是有一些区别：

\begin{bash}
mkfifo /ipc/namedfifo
ls -l /ipc/namefifo
\end{bash}
可以看出namedfifo的属性中有一个p，表示这是一个管道。

\textbf{FIFO操作}

对命名管道FIFO来说，IO操作和普通的IO操作基本上是一样的，二者之间存在一个主要的区别。在FIFO中，必须使用一个open()函数来显式的建立连接到管道的通道。一般来说FIFO总是处于阻塞状态。也就是说，如果命名管道FIFO打开时设置了读权限，则读进程将一直阻塞，一直到其他进程打开该FIFO并且向管道中写入数据。这个阻塞动作反过来也是成立的，如果一个进程打开一个管道写入数据，当没有进程网管道读取数据的时候，写管道操作也是阻塞的，知道已经写入的数据被读出后，才能进行写入操作。如果不希望在进行命名管道操作的时候发生阻塞，可以再open()调用中使用O\_NONBLOCK标志，以关闭默认的阻塞动作。

\subsubsection{消息队列}
消息队列是内核地址空间的内部链表，通过linux内核在各个进程之间传递内容，消息顺序地发送到消息队列中，并以几种不同的方式从队列中获取，每个消息队列可以用IPC标识符唯一的进行标识。内核中消息队列是通过IPC的标识符来区别的，不同的消息队列之间是相对独立的，每个消息队列中的消息，有构成一个独立的链表。

\subsection{线程}
多线程是多任务处理的一种特殊形式，多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下，两种类型的多任务处理：基于进程和基于线程。

基于进程的多任务处理是程序的并发执行。
基于线程的多任务处理是同一程序的片段的并发执行。
多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程，每个线程定义了一个单独的执行路径。

本教程假设您使用的是 Linux 操作系统，我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用，比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。
\subsubsection{创建线程}
使用下面创建线程:
\begin{cpp}
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)
\end{cpp}
\begin{table}[!ht]
\begin{tabular}{lll}
参数&描述\\
thread&指向线程标识符指针\\
attr&一个不透明的属性对象，可被用来设置显成熟型。可以指定线程属性对象，可以刻使用默认值NULL\\
stard\_routine&线程运行函数其实地址，一旦线程被创建就会执行\\
arg&运行哈拿书的参数。它必须通过把引用作为指针强制转换为void类型进行传递。如果没有传递参数，则使用NULL
\end{tabular}
\end{table}
创建进程成功时，函数返回0，若返回值不为0则说明创建线程失败。
\subsubsection{终止线程}
使用下面的程序，我们可以用它来终止一个 POSIX 线程：
\begin{cpp}
#include <pthread.h>
pthread_exit (status)
\end{cpp}
在这里，pthread\_exit 用于显式地退出一个线程。通常情况下，pthread\_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束，并通过 pthread\_exit() 退出，那么其他线程将继续执行。否则，它们将在 main() 结束时自动被终止。


多线程代码:
\cppfile{cxxcode/thread/ex9/main.cpp}
\begin{cmake}
cmake_minimum_required(VERSION 3.10)
project(ex9)
# 设置C++编译器为clang++
SET(CMAKE_CXX_COMPILER "clang++")
# 构建Release，占用空间更小
set(CMAKE_BUILD_TYPE  Release)
# CMAKE 编译flag，设置C++11，优化为O3,添加线程库
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -march=native -O3 -pthread" )
# 设置C++标准为C++11
set(CMAKE_CXX_STANDARD 11)

add_executable(ex9 main.cpp) 

\end{cmake}

